package org.dreamwork.assistant.tools.cli.fx;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import org.dreamwork.assistant.tools.cli.Distributor;
import org.dreamwork.concurrent.Looper;
import org.dreamwork.gson.GsonHelper;
import org.dreamwork.util.StringUtil;

import java.io.*;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class MainController implements Initializable {
//    public TextField txtName;
    public ComboBox<String> cbName;
    public TextField txtPom;
    public TextField txtMainClass;
    public TextField txtJavaHome;
    public TextField txtMavenRepo;
    public TextField txtOutDir;
    public TextField txtConfigFile;
    public TextField txtDebugPort;

    public Button btnPom;
    public Button btnJavaHome;
    public Button btnMavenRepo;
    public Button btnOutDir;
    public Button btnAddInclude;
    public Button btnRemoveInclude;
    public Button btnConfigFile;
    public Button btnDistribute;
    public Button btnCancel;

    public CheckBox btnDebugPort;
    public CheckBox btnForceInclude;
    public CheckBox btnCompileDependencies;
    public CheckBox btnWindowScript;
    public CheckBox btnLinuxScript;

    public CheckBox btnRecursive;
    public ListView<String> listInclude;
    public TextArea txtLog;
    public Hyperlink lnkSource;
    public TextField txtJavaOpts;
    public TextField txtArgs;
    public MenuItem itmClearLog;

    private File current_dir;
    private Stage stage;
    private FileChooser chooser = new FileChooser ();
    private DirectoryChooser directoryChooser = new DirectoryChooser ();

    private Map<String, Map<String, String>> cache;
    private static final Type CACHE_TYPE = new TypeToken<Map<String, Map<String, String>>> () {}.getType ();
    private static final String LOOPER_NAME = "non-ui";

    void setStage (Stage stage) {
        this.stage = stage;
        if (stage != null) {
            stage.setOnCloseRequest (e -> {
                e.consume ();
                confirmToQuit ();
            });
        }
    }

    private void confirmToQuit () {
        String message = "Are you sure to quit?";
        DialogHelper.prompt (message).ifPresent (type -> {
            if (type == ButtonType.OK) {
                Looper.exit ();

                System.exit (0);
            }
        });
    }

    @FXML private void cancel () {
        if (stage != null) {
            confirmToQuit ();
        }
    }

    @FXML private void perform (ActionEvent event) throws IOException {
        ButtonBase button = (ButtonBase) event.getSource ();
        switch (button.getId ()) {
            case "btnPom":
                selectFile (txtPom);
                break;
            case "btnJavaHome":
                selectDir (txtJavaHome);
                break;
            case "btnMavenRepo":
                selectDir (txtMavenRepo);
                break;
            case "btnOutDir":
                selectDir (txtOutDir);
                break;
            case "btnAddInclude":
                selectDir (listInclude);
                break;
            case "btnRemoveInclude":
                ObservableList<String> items = listInclude.getSelectionModel ().getSelectedItems ();
                if (items != null && !items.isEmpty ()) {
                    listInclude.getItems ().removeAll (items);
                }
                break;
            case "btnConfigFile":
                selectFile (txtConfigFile);
                break;
            case "btnDistribute":
                distribute ();
                break;
            case "btnDebugPort":
                txtDebugPort.setDisable (!btnDebugPort.isSelected ());
                break;
            case "lnkSource":
                showSource ();
                break;
        }
    }

    private void showSource () {
        try {
            java.awt.Desktop.getDesktop ().browse (
                    URI.create ("https://gitee.com/seth_yang/dreamwork-assistant-tools")
            );
        } catch (IOException ex) {
            DialogHelper.error (ex.getMessage (), ex);
        }
    }

    private void selectDir (TextField field) throws IOException {
        String path = select (true, field.getText ());
        if (!StringUtil.isEmpty (path))
            field.setText (path);
    }

    private void selectDir (ListView<String> view) throws IOException {
        String path = select (true, null);
        ObservableList<String> items = view.getItems ();
        if (!StringUtil.isEmpty (path) && !items.contains (path)) {
            view.getItems ().add (path);
        }
    }

    private void selectFile (TextField field) throws IOException {
        String path = field.getText ();
        if (!StringUtil.isEmpty (path)) {
            File parent = new File (path).getParentFile ();
            path = select (false, parent.getCanonicalPath ());
        } else {
            path = select (false, null);
        }
        if (!StringUtil.isEmpty (path))
            field.setText (path);
    }

    private void distribute () {
        List<String> list = new ArrayList<> ();
        Map<String, String> map = new HashMap<> ();

//        String name = txtName.getText ();
        String name = cbName.getSelectionModel ().getSelectedItem ();
        if (StringUtil.isEmpty (name)) {
            DialogHelper.error ("Please select or input a name");
            return;
        }

        String pom = txtPom.getText ();
        if (!StringUtil.isEmpty (pom)) {
            list.add ("-p");
            list.add (pom.trim ());
            map.put ("p", pom.trim ());
        }

        String home = txtJavaHome.getText ();
        if (!StringUtil.isEmpty (home)) {
            home = home.trim ();
            if (home.contains (" ")) {
                home = '"' + home + '"';
            }
            list.add ("-j");
            list.add (home);
            map.put ("j", home);
        }

        String repo = txtMavenRepo.getText ();
        if (!StringUtil.isEmpty (repo)) {
            list.add ("-r");
            list.add (repo.trim ());
            map.put ("r", repo.trim ());
        }

        String main = txtMainClass.getText ();
        if (!StringUtil.isEmpty (main)) {
            list.add ("-m");
            list.add (main.trim ());
            map.put ("m", main.trim ());
        }

        String out = txtOutDir.getText ();
        if (!StringUtil.isEmpty (out)) {
            list.add ("-o");
            list.add (out.trim ());
            map.put ("o", out.trim ());
        }

        ObservableList<String> items = listInclude.getItems ();
        if (items != null && !items.isEmpty ()) {
            StringBuilder builder = new StringBuilder ();
            for (String item : items) {
                if (builder.length () > 0) builder.append (':');
                builder.append (item);
            }
            list.add ("-i");
            list.add (builder.toString ());
            map.put ("i", builder.toString ());
        }

        String conf = txtConfigFile.getText ();
        if (!StringUtil.isEmpty (conf)) {
            list.add ("-C");
            list.add (conf);
            map.put ("C", conf);
        }

        String opts = txtJavaOpts.getText ();
        if (!StringUtil.isEmpty (opts)) {
            opts = opts.trim ();
            opts = opts.replace ("-", "\\-");
            list.add ("-V");
            list.add (opts);
            map.put ("V", opts);
        }

        String args = txtArgs.getText ();
        if (!StringUtil.isEmpty (args)) {
            args = args.trim ().replace ("-", "\\-");
            list.add ("-a");
            list.add (args);
            map.put ("a", args);
        }

        if (btnCompileDependencies.isSelected ()) {
            list.add ("-c");
            map.put ("c", "true");
        }
        if (btnRecursive.isSelected ()) {
            list.add ("-R");
            map.put ("R", "true");
        }
        if (btnForceInclude.isSelected ()) {
            list.add ("-f");
            map.put ("f", "true");
        }
        if (btnDebugPort.isSelected ()) {
            String s_port = txtDebugPort.getText ();
            try {
                int port = Integer.parseInt (s_port.trim ());
                if (port > 1024 && port < 65535) {
                    list.add ("--gen-debug-script");
                    list.add ("--debug-port=" + port);

                    map.put ("--gen-debug-script", "true");
                    map.put ("--debug-port", s_port.trim ());
                }
            } catch (Exception ex) {
                ex.printStackTrace ();
            }
        }
        if (btnWindowScript.isSelected ()) {
            list.add ("--with-win-script");
            map.put ("--with-win-script", "true");
        }
        if (btnLinuxScript.isSelected ()) {
            list.add ("--with-linux-script");
            map.put ("--with-linux-script", "true");
        }

        if (cache == null) {
            cache = new HashMap<> ();
        }
        cache.put (name, map);
        Looper.runInLoop (LOOPER_NAME, ()->{
            saveCache (name);
            distribute (list);
        });
    }

    private void distribute (List<String> params) {
        String[] args = new String[params.size ()];
        args = params.toArray (args);
        System.setOut (loggerStream);
        try {
            Distributor.main (args);
        } catch (Exception ex) {
            DialogHelper.error (ex.getMessage (), ex);
        }
    }

    private String select (boolean dir, String base) throws IOException {
        File file;

        if (dir) {
            if (!StringUtil.isEmpty (base)) {
                directoryChooser.setInitialDirectory (new File (base));
            } else if (current_dir != null) {
                directoryChooser.setInitialDirectory (current_dir);
            }
            file = directoryChooser.showDialog (stage);
        } else {
            if (!StringUtil.isEmpty (base)) {
                chooser.setInitialDirectory (new File (base));
            } else if (current_dir != null) {
                chooser.setInitialDirectory (current_dir);
            }
            file = chooser.showOpenDialog (stage);
        }
        if (file != null) {
            if (file.isDirectory ()) {
                current_dir = file;
            } else {
                current_dir = file.getParentFile ();
            }
            return file.getCanonicalPath ();
        }

        return "";
    }

    private void loadCache () {
        File dir = new File (".cache");
        if (!dir.exists () && !dir.mkdirs ()) {
            cache = new HashMap<> ();
            return;
        }
        File file = new File (dir, "dist");
        if (file.exists ()) {
            try (InputStream in = new FileInputStream (file)) {
                Gson g = GsonHelper.getGson ();
                Reader reader = new InputStreamReader (in, StandardCharsets.UTF_8);
                cache = g.fromJson (reader, CACHE_TYPE);
            } catch (IOException ex) {
                ex.printStackTrace ();
                DialogHelper.error (ex.getMessage (), ex);
            }
        } else {
            cache = new HashMap<> ();
        }

        Platform.runLater (()-> {
            List<String> list = new ArrayList<> (cache.keySet ());
            Collections.sort (list);
            cbName.getItems ().addAll (list);
        });
    }

    private void autoFill (String name) {
        if (cache != null && cache.containsKey (name)) {
            Map<String, String> map = cache.get (name);

            if (map.containsKey ("p")) {
                txtPom.setText (map.get ("p").trim ());
            }
            if (map.containsKey ("j")) {
                txtJavaHome.setText (map.get ("j").trim ());
            }
            if (map.containsKey ("r")) {
                txtMavenRepo.setText (map.get ("r").trim ());
            }
            if (map.containsKey ("m")) {
                txtMainClass.setText (map.get ("m").trim ());
            }
            if (map.containsKey ("o")) {
                txtOutDir.setText (map.get ("o").trim ());
            }
            if (map.containsKey ("i")) {
                String tmp = map.get ("i").trim ();
                String[] a = tmp.split (":");
                listInclude.getItems ().clear ();
                listInclude.getItems ().addAll (a);
            }
            if (map.containsKey ("C")) {
                txtConfigFile.setText (map.get ("C").trim ());
            }
            if (map.containsKey ("a")) {
                String args = map.get ("a").trim ();
                args = args.replace ("\\-", "-");
                txtArgs.setText (args);
            }
            if (map.containsKey ("V")) {
                String java_opts = map.get ("V").trim ();
                java_opts = java_opts.replace ("\\-", "-");
                txtJavaOpts.setText (java_opts);
            }

            btnCompileDependencies.setSelected (map.containsKey ("c"));
            btnRecursive.setSelected (map.containsKey ("R"));
            btnForceInclude.setSelected (map.containsKey ("f"));
            if (map.containsKey ("--debug-port")) {
                String tmp = map.get ("--debug-port");
                try {
                    Integer.parseInt (tmp.trim ());
                    txtDebugPort.setText (tmp.trim ());
                    txtDebugPort.setDisable (false);
                    btnDebugPort.setSelected (true);
                } catch (Exception ex) {
                    btnDebugPort.setSelected (false);
                    txtDebugPort.setText ("");
                }
            } else {
                btnDebugPort.setSelected (false);
                txtDebugPort.setText ("");
                txtDebugPort.setDisable (true);
            }
            btnWindowScript.setSelected (map.containsKey ("--with-win-script"));
            btnLinuxScript.setSelected (map.containsKey ("--with-linux-script"));
        }
    }

    private void saveCache (String name) {
        try {
            File dir = new File (".cache");
            if (!dir.exists () && !dir.mkdirs ()) {
                return;
            }
            File file = new File (dir, "dist");
            try (Writer out = new FileWriter (file)) {
                Gson g = new GsonBuilder ().setPrettyPrinting ().create ();
                g.toJson (cache, out);
                out.flush ();
            }
            Platform.runLater (()-> {
                List<String> list = new ArrayList<> (cache.keySet ());
                Collections.sort (list);
                ObservableList<String> items = cbName.getItems ();
                items.clear ();
                items.addAll (list);

                cbName.getSelectionModel ().select (name);
            });
        } catch (IOException ex) {
            DialogHelper.error (ex.getMessage (), ex);
        }
    }

    @Override
    public void initialize (URL location, ResourceBundle resources) {
        cbName.setOnAction (e->{
            String name = cbName.getSelectionModel ().getSelectedItem ();
            if (!StringUtil.isEmpty (name)) {
                completeOutputDir ();
            }
        });
        Looper.create (LOOPER_NAME, 16);
        Looper.runInLoop (LOOPER_NAME, this::loadCache);
    }

    private void completeOutputDir () {
        String name = cbName.getSelectionModel ().getSelectedItem ();
        if (!StringUtil.isEmpty (name)) {
            String path = txtOutDir.getText ();
            if (!StringUtil.isEmpty (path)) {
                if (!path.endsWith ("/" + name)) {
                    if (path.charAt (path.length () - 1) == '/') {
                        txtOutDir.setText (path + name);
                    } else {
                        txtOutDir.setText (path + '/' + name);
                    }
                }
            }

            autoFill (name);
        }
    }

    private void appendLog (String message) {
        if (Platform.isFxApplicationThread ()) {
            doAppendLog (message);
        } else {
            Platform.runLater (()-> doAppendLog (message));
        }
    }

    private void doAppendLog (String message) {
        txtLog.appendText (message);
        txtLog.appendText ("\r\n");
    }

    private final PrintStream loggerStream = new PrintStream (new ByteArrayOutputStream ()) {
        @Override
        public void println (String x) {
            appendLog (x);
        }

        @Override
        public PrintStream printf (String format, Object... args) {
            appendLog (String.format (format, args));
            return super.printf (format, args);
        }
    };

    public void clearLog () {
        txtLog.clear ();
    }
}
